<?php
/**
 * This file is part of workerman.
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the MIT-LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @author walkor<walkor@workerman.net>
 * @copyright walkor<walkor@workerman.net>
 * @link http://www.workerman.net/
 * @license http://www.opensource.org/licenses/mit-license.php MIT License
 */

/**
 * 用于检测业务代码死循环或者长时间阻塞等问题
 * 如果发现业务卡死，可以将下面declare打开（去掉//注释），并执行php start.php reload
 * 然后观察一段时间workerman.log看是否有process_timeout异常
 */
//declare(ticks=1);

/**
 * 聊天主逻辑
 * 主要是处理 onMessage onClose 
 */
use \GatewayWorker\Lib\Gateway;
use Workerman\Lib\Timer;
//use \Workerman\MySQL;
require_once 'vendor/mysql-master/src/Connection.php';
//require_once '/data/wwwroot/project/public/applet/public/gateway-worker/vendor/mysql-master/src/Connection.php';

class Events
{
    public static $db = null;
    
    public function __construct() {
        header('Access-Control-Allow-Origin: *');
        header('Content-Type:application/json; charset=utf-8');
    }
   
    public static function onWorkerStart() {
        // 连接数据库
        $host = 'gz-cdb-kwizlzdl.sql.tencentcdb.com';
        $port = '63942';
        $user = 'wx';
        $password = 'wx@20170808';
        $db_name = 'weixin';
        self::$db = new Workerman\MySQL\Connection($host, $port, $user, $password, $db_name);
    }
   
   /**
    * 有消息时
    * @param int $client_id
    * @param mixed $message
    */
   public static function onMessage($client_id, $message) {
        // debug
        echo "client:{$_SERVER['REMOTE_ADDR']}:{$_SERVER['REMOTE_PORT']} gateway:{$_SERVER['GATEWAY_ADDR']}:{$_SERVER['GATEWAY_PORT']}  client_id:$client_id session:".json_encode($_SESSION)." onMessage:".$message."\n";
        
        // 客户端传递的是json数据
        $message_data = json_decode($message, true);
        
        if(!$message_data) {
            return ;
        }
                
        // 根据类型执行不同的业务
        switch($message_data['type']) {
            case 'pong':
                // 客户端回应服务端的心跳
                return;
            case 'login':
                // 登陆房间 - 客户端登录 message格式: {"room_id":"24", "nickname":"Yangpan", "headimg":"http://api.wei.com/1.jpg", "user_id":"1", "type":"login"}
                // 判断是否有房间号
                if(!isset($message_data['room_id'])) {
                    throw new \Exception("\$message_data['room_id'] not set. client_ip:{$_SERVER['REMOTE_ADDR']} \$message:$message");
                }
                
                // 判断用户是否存在 ???
                
                // 房间在后台是否存在
                $res = self::$db->select('*')->from('qw_20171009_room')->where('id=' . $message_data['room_id'])->row();
                
                if (!$res) {
                    self::err('room id error', [], 'login');
                    return;
                }
                
                // 根据房间号查所有桌子信息
                $field = 'id, name, number, money, money_type';
                $desks = self::$db->select($field)->from('qw_20171009_desk')->where('parent_id=' . $message_data['room_id'])->query();
                
                // 根据桌子ID查出每张桌子的当前人数
                foreach ($desks as $key => $value) {
                    $desks[$key]['current_users'] = count(Gateway::getClientIdByUid($value['id']));
                    $desks[$key]['fist_number'] = log($value['number'], 2);     // 每次比赛的局数 
                }
                
                // 把房间号昵称放到session中
                $room_id = $message_data['room_id'];
                $nickname = htmlspecialchars($message_data['nickname']);
                $_SESSION['room_id'] = $room_id;
                $_SESSION['nickname'] = $nickname;                    // 昵称
                $_SESSION['headimg'] = $message_data['headimg'];      // 头像
                $_SESSION['user_id'] = $message_data['user_id'];      // 用户ID

                var_dump($desks);
                
                Gateway::joinGroup($client_id, $room_id);
                
                self::suc('suc', $desks, 'login');
                
                return;
            case 'login_desk':
                // 进入桌子 - 格式 ：{"desk_id":"1", "type":"login_desk"}
                                
                if(!isset($message_data['desk_id'])) {
                    throw new \Exception("\$message_data['desk_id'] not set. client_ip:{$_SERVER['REMOTE_ADDR']} \$message:$message");
                    self::err('desk id not exist', [], 'login_desk');
                    return;
                }
                
                // 用户不存在 - 获取当前用户信息
                $user = Gateway::getSession($client_id);
                
                if(!$user) {
                    throw new \Exception("\user not set. client_ip:{$_SERVER['REMOTE_ADDR']} \$message:$message");
                    self::err('user not exist', [], 'login_desk');
                    return;
                }
                
                // 判断桌子是否已经创建 
                $desk = self::$db->select('*')->from('qw_20171009_desk')->where('id=' . $message_data['desk_id'])->row();
                
                if (!$desk) {
                    self::err('desk not exist', [], 'login_desk');
                    return;
                }
                                                
                // 获取桌子内所有用户列表 - 返回一个client_id的数组 - 防止用户重复加入桌子
                $clients_list = Gateway::getClientIdByUid($message_data['desk_id']);
                
                if (array_search($client_id, $clients_list)) {
                    //self::err('do not repeat the desk', [], 'login_desk');
                    return;
                }
                
                // 同一个用户用不同设备登陆，会导致在一个房间 
                foreach ($clients_list as $value) {
                    $clientUsers = Gateway::getSession($value);
                    
                    if ($clientUsers['user_id'] == $user['user_id']) {
                        self::err('already login desk', [], 'login_desk');
                        return;
                    }
                }
                
                // 用户入桌金额是否足够 
                $userMess = self::$db->select('money, jifen, coin')->from('qw_20171009_user')->where('id='.$_SESSION['user_id'])->row();
                
                // 扣费类型 - coin：扣文明币，money：扣钱
                $updateSql = '';
                if ($desk['money_type'] == 'coin') {
                    if ($userMess['coin'] < $desk['money']) {
                        self::err('coin not enough', [], 'login_desk');
                        return;
                    }
                    
                    $updateSql = "update qw_20171009_user set coin = coin - {$desk['money']} where id = {$user['user_id']}"; 
                } else {
                    if ($userMess['money'] < $desk['money']) {
                        self::err('money not enough', [], 'login_desk');
                        return;
                    }
                    
                    $updateSql = "update qw_20171009_user set money = money - {$desk['money']} where id = {$user['user_id']}";
                }
                
                // 减少币或者钱
                self::$db->query($updateSql);
                
                // 总人数 （未加入当前用户）
                $count_user = count($clients_list);
                
                // 桌子总人数已满
                if ($count_user >= $desk['number']) {
                    self::err('desk full', [], 'login_desk');
                    return;
                }
                
                $data = [];
                $data['start_game'] = 0;
                
                // 特殊处理 ，最后一人进入桌子  - 广播桌子内的用户，可以开始游戏了
                if ($count_user + 1 == $desk['number']) {
                    // 如果用户没有出拳 - 默认给0
                    $room_id = $user['room_id'];
                    $data['start_game'] = 1;        // 是否开始游戏
                    $data['setup_time'] = 15;       // 游戏准备时间 - 不同人数时间不一样，人数越多时间越长
                    
                    // 倒计时 - 15 + 5 = 20 秒后，给桌子成员广播比赛结果，然后踢出桌子
                    Timer::add(20, function()use($room_id, $desk){
                        // 算法 - 猜拳、扑克牌
                        
                        // 获取桌子所有用户信息，并拼接成如下数据格式
                        /*
                            [
                                ['user_id'=>111, 'nickname'=>'', 'headimg'=>'', 'data'=>[[1,3,3], [2,2,3]]],
                                ['user_id'=>222, 'nickname'=>'', 'headimg'=>'', 'data'=>[[1,2,3], [2,3,3]]]
                            ];
                        */
                        
                        // 拼接好的数据
                        $cliens = [];
                        
                        // 获取桌子内所有成员信息  - ['7f00000108fc00000008','7f00000108fc00000009']
                        $list = Gateway::getClientIdByUid($desk['id']);
                        
                        foreach ($list as $value) {
                            $userData = Gateway::getSession($value);
                            
                            // 如果没有出拳 - 默认为 0
                            if (empty($userData['data'])) {
                                // 2 ^ x = 4 使用log函数求出对数
                                for ($i = 0; $i < log($desk['number'], 2); $i++) {
                                    $userData['data'][$i] = [0,0,0];
                                }
                            }
                            
                            $cliens[] = $userData;
                        }
                        
                        // 查询房间信息
                        $roomMsg = self::$db->select('*')->from('qw_20171009_room')->where('id=' . $room_id)->row();
                        
                        // 计算出拳次数 - 1:群雄夺宝, 2:拳王争霸
                        $fistNum = $roomMsg['parent_id'] == 1 ? 3 : 1;
                        
                        // 计算排名，输赢
                        $result = self::confrontation($cliens, $fistNum);
                        
                        // 总结果写入表
                        $insertData = [];
                        $insertData['room_id'] = $room_id;
                        $insertData['desk_id'] = $desk['id'];
                        $insertData['number'] = $desk['number'];
                        $insertData['money'] = $desk['number'] * $desk['money'];
                        $insertData['game_type'] = $roomMsg['parent_id'];
                        $insertData['result'] = json_encode($result);
                        $insertData['create_time'] = time();
                        $insert_id = self::$db->insert('qw_20171009_game_log')->cols($insertData)->query();
                        
                        // 给赢的用户增加用户积分 - 在“confrontation”函数中处理
                        
                        $class = [
                            1 => '群雄夺宝',
                            2 => '拳王争霸',
                        ];
                        
                        $deskMoneyTotal = $desk['money'] * $desk['number'];     // 桌子总金额
                        $bonusTotal = sprintf('%.2f', $deskMoneyTotal * 0.9);   // 奖金总金额 
                        
                        // 再为每个用户写入操作记录
                        foreach ($result['users'] as $kUserId => $value) {
                            $insertData = [];
                            $insertData['room_id'] = $room_id;
                            $insertData['title'] = $class[$roomMsg['parent_id']] . '-' . $roomMsg['name'] . '-' . $desk['name'];
                            $insertData['desk_id'] = $desk['id'];
                            $insertData['user_id'] = $kUserId;
                            $insertData['result_id'] = $insert_id;
                            $insertData['res'] = $result['ranking'][0]['user_id'] == $kUserId ? 1 : 0; 
                            $insertData['integral'] = $insertData['res'] == 1 ? 3 : 0;     // 处理积分  胜3分，平1分，负0分
                            $insertData['game_type'] = $roomMsg['parent_id'];
                            $insertData['money'] = $desk['money'];
                            
                            // 写入比赛排名
                            foreach ($result['ranking'] as $k => $v) {
                                if ($v['user_id'] == $kUserId) {
                                    $insertData['ranking'] = $k + 1;
                                    $insertData['nickname'] = $v['nickname'];
                                    $insertData['headimgurl'] = $v['headimg'];
                                    
                                    // 拳王争霸奖金：每级每组联赛的总报名费的10%（幅度5%~10%由管理员调整）作为平台服务费，余下金额的50%为冠军奖金、30%为亚军奖金、20%为季军奖金。 ???
                                    if ($roomMsg['parent_id'] == 2) {
                                        if ($k == 0) {
                                            $bonus = sprintf('%.2f', $bonusTotal * 0.5);
                                        } elseif ($k == 1) {
                                            $bonus = sprintf('%.2f', $bonusTotal * 0.3);
                                        } elseif ($k == 3) {
                                            $bonus = sprintf('%.2f', $bonusTotal * 0.2);
                                        }
                                
                                        if ($k <= 2) {
                                            self::$db->query("update qw_20171009_user set money = money + $bonus where id = {$v['user_id']}");
                                            $insertData['bonus'] = $bonus;
                                        }
                                    }
                                    
                                    break;
                                }
                            }
                            
                            $insertData['result'] = json_encode($value);
                            $insertData['create_time'] = time();
                            self::$db->insert('qw_20171009_user_game_log')->cols($insertData)->query();
                        }
                        
                        // 到了结果时间给桌子成员广播消费结果
                        $new_message = ['status'=>1, 'info'=>'suc', 'data'=>$result, 'type'=>'game_result'];
                        Gateway::sendToUid($desk['id'], json_encode($new_message));
                        
                        // 注销用户信息游戏数据，踢出桌子
                        foreach ($list as $value) {
                            Gateway::unbindUid($value, $desk['id']);
                            Gateway::updateSession($value, array('desk_id'=>null, 'data'=>null));
                        }
                        
                        // 胜者获取奖品
                        if (isset($result['ranking'][0])) {
                            // 获取房间奖品
                            $prize = self::$db->select('name')->from('qw_20171009_prize')->where("room_id = $room_id")->row();
                            
                            // 如果不存在就使用默认奖品
                            if (!$prize) {
                                $prize = self::$db->select('name')->from('qw_20171009_prize')->where("room_id = 0")->row();
                            }
                            
                            $prizeData = [];
                            $prizeData['user_id'] = $result['ranking'][0]['user_id'];
                            $prizeData['gift_name'] = $prize['name'];
                            $prizeData['create_time'] = time();
                            self::$db->insert('qw_20171009_user_gift')->cols($prizeData)->query();
                        }
                        
                        return;
                    }, [], false);            
                }
                                
                $data['desk_id'] = $_SESSION['desk_id'] = $message_data['desk_id'];
                $data['count_users'] = $count_user + 1;
                
                $new_message = ['status'=>1, 'info'=>'suc', 'type'=>'login_desk', 'data'=>$data];
                
                // 用户与桌子绑定
                Gateway::bindUid($client_id, $message_data['desk_id']);
                
                // 发送信息给UID下的所有用户
                Gateway::sendToUid($message_data['desk_id'], json_encode($new_message));
                
                // 发送信息给房间 - xx桌子有人数变化
                $new_message['type'] = 'user_login_desk';
                Gateway::sendToGroup($user['room_id'], json_encode($new_message));
                
                return;
            case 'say':
                // 客户端发言 message: {type:say, to_client_id:xx, content:xx}
                // 非法请求
                if(!isset($_SESSION['room_id']))
                {
                    throw new \Exception("\$_SESSION['room_id'] not set. client_ip:{$_SERVER['REMOTE_ADDR']}");
                }
                $room_id = $_SESSION['room_id'];
                $nickname = $_SESSION['nickname'];
                
                // 私聊
                if($message_data['to_client_id'] != 'all')
                {
                    $new_message = array(
                        'type'=>'say',
                        'from_client_id'=>$client_id, 
                        'from_nickname' =>$nickname,
                        'to_client_id'=>$message_data['to_client_id'],
                        'content'=>"TO you say ".nl2br(htmlspecialchars($message_data['content'])),
                        'time'=>date('Y-m-d H:i:s'),
                    );
                    Gateway::sendToClient($message_data['to_client_id'], json_encode($new_message));
                    $new_message['content'] = "You to ".htmlspecialchars($message_data['to_nickname'])." say: ".nl2br(htmlspecialchars($message_data['content']));
                    return Gateway::sendToCurrentClient(json_encode($new_message));
                }
                
                // 群聊
                $new_message = array(
                    'type'=>'say', 
                    'from_client_id'=>$client_id,
                    'from_nickname' =>$nickname,
                    'to_client_id'=>'all',
                    'content'=>nl2br(htmlspecialchars($message_data['content'])),
                    'time'=>date('Y-m-d H:i:s'),
                );
                
                return Gateway::sendToGroup($room_id ,json_encode($new_message));
            case 'bet':
                // 下注 - 保存数据到session，保存下注数据到数据库 （开奖后会保存） - {"type":"bet", "data":[[1,2,3],[2,3,1]]}
                
                // 未进入桌子
                if (empty($_SESSION['desk_id'])) {
                    self::err('not login desk', [], 'bet');
                    return;
                }
                
                $_SESSION['data'] = $message_data['data'];
                self::suc('suc', [], 'bet');
                return;
        }
   }
   
   /**
    * 当客户端断开连接时
    * @param integer $client_id 客户端id
    */
    public static function onClose($client_id) {
        // debug
        echo "client:{$_SERVER['REMOTE_ADDR']}:{$_SERVER['REMOTE_PORT']} gateway:{$_SERVER['GATEWAY_ADDR']}:{$_SERVER['GATEWAY_PORT']}  client_id:$client_id onClose:''\n";
       
       // 从房间的客户端列表中删除
       if(isset($_SESSION['room_id'])) {
            $room_id = $_SESSION['room_id'];
            $new_message = array (
                'type'=>'logout', 
                'from_client_id'=>$client_id, 
                'from_nickname'=>$_SESSION['nickname'], 
                'time'=>date('Y-m-d H:i:s')
            );
            Gateway::sendToGroup($room_id, json_encode($new_message));
           
            // 如果桌子存在，就通知桌子里的人xxx退出
            if (isset($_SESSION['desk_id'])) {
                $desk_id = $_SESSION['desk_id'];
                $new_message['type'] = 'logout_desk';
                $new_message['desk_id'] = $desk_id;
                Gateway::sendToUid($desk_id, json_encode($new_message));
            }
        }
    }
    
    /**
     * 对决
     *
     * 2人比赛一维数组，4人比赛二维数组，8人比赛三维数组，16人比赛四维数据
     * [
     *      ['user_id'=>1, 'data'=>[[1,2,3], [2,3,3]]],
     *      ['user_id'=>2, 'data'=>[[1,2,3], [2,3,3]]],
     *      ['user_id'=>3, 'data'=>[[1,2,3], [2,3,3]]],
     *      ['user_id'=>4, 'data'=>[[1,2,3], [2,3,3]]],
     * ]
     * 
     * 最后拼接与
     * result/ranking/users
     * 
     */
    public static function confrontation($datas, $fistNum) {
        // 用户数据
        $data = $datas;
        
        // 结果列表
        $result = [];
        
        // 排行列表
        $ranking = [];
        
        // 用户列表，用于记录到数据库
        $users = [];
        
        // 比赛次数
        $game_nums = count($data[0]['data']);
        
        // 循环比赛次数
        for ($i = 0; $i < $game_nums; $i++) {
            // 去除输了的用户，重新排序数组
            sort($data);
    
            // 循环比赛人数
            for ($people = 0; $people < count($data); $people += 2) {
                // 比拳的大小
                $playingCard = [];
                $res = self::fistThanSize($data[$people]['data'][$i], $data[$people+1]['data'][$i], $playingCard);
                
                // 随机给用户的扑克牌
                $data[$people]['data'][$i][] = $playingCard['one']['color'].$playingCard['one']['number'];
                $data[$people+1]['data'][$i][] = $playingCard['two']['color'].$playingCard['two']['number'];
                
                // 用于 - 记录到数据库
                $users[$data[$people]['user_id']][$people]['fight'] = [$data[$people]['data'][$i], $data[$people+1]['data'][$i]];
                $users[$data[$people+1]['user_id']][$people]['fight'] = [$data[$people+1]['data'][$i], $data[$people]['data'][$i]];
                
                // 用户对决数据
                $result[$i][$data[$people]['user_id']]['fight'] = [$data[$people]['data'][$i], $data[$people+1]['data'][$i]];
                $result[$i][$data[$people+1]['user_id']]['fight'] = [$data[$people+1]['data'][$i], $data[$people]['data'][$i]];
                
                if ($res) {
                    $users[$data[$people]['user_id']][$people]['res'] = $result[$i][$data[$people]['user_id']]['res'] = 1;
                    $users[$data[$people+1]['user_id']][$people]['res'] = $result[$i][$data[$people+1]['user_id']]['res'] = 0;
                    unset($data[$people+1]);
                } else {
                    $users[$data[$people]['user_id']][$people]['res'] = $result[$i][$data[$people]['user_id']]['res'] = 0;
                    $users[$data[$people+1]['user_id']][$people]['res'] = $result[$i][$data[$people+1]['user_id']]['res'] = 1;
                    unset($data[$people]);
                }
            }
        }
        
        // 排行
        foreach (end($result) as $key => $value) {
            foreach ($datas as $k => $v) {
                if ($key == $v['user_id'] and $value['res'] == 1) {
                    $one = $k;
                } 
                
                if ($key == $v['user_id'] and $value['res'] == 0) {
                    $two = $k;
                }
            }
        }
        
        // 计算排名
        // 为第一名增加积分
        self::$db->query('update qw_20171009_user set jifen = jifen + 3 where id = ' . $datas[$one]['user_id']);
        $ranking[] = $datas[$one];
        unset($datas[$one]);
        
        // 两人对战只有$one，没有$two
        if (isset($two)) {
            $ranking[] = $datas[$two];
            unset($datas[$two]);
        }
        
        shuffle($datas);
        $ranking = array_merge($ranking, $datas);
        
        $returnData = [];
        $returnData['ranking'] = $ranking;
        $returnData['result'] = $result;
        $returnData['users'] = $users;

        return $returnData;
    }
    
    /**
     * 划拳比大小
     * 格式 - [1, 2, 3] VS [3, 2, 1]
     * 3：石头，2：剪刀，1：布
     * @return true: one赢，否则one输
     */
    public static function fistThanSize($one, $two, &$playingCard) {
        // 获胜规则
        $winning_rules = [
            [3, 2],
            [2, 1],
            [1, 3]
        ];
    
        $res = [];
        $result = 0;
    
        // 循环每次出拳 - 三次/一次
        for ($i = 0; $i < count($one); $i++) {
            $res[$i] = 0;
    
            // 如果没有出拳  - Timer中过滤掉
            
            // 随机扑克牌
            $card =  self::playingCards($playingCard);
            
            // 平手
            if ($one[$i] == $two[$i]) {
                // 扑克牌比大小 - 系统随机分配
                if ($card) {
                    $res[$i] = 1;
                } else {
                    $res[$i] = 0;
                }
            } elseif (in_array([$one[$i], $two[$i]], $winning_rules)) {
                $res[$i] = 1;
            } else {
                $res[$i] = 0;
            }
            
            $result += $res[$i];
        }
            
        // one 赢
        if ($result > 1) {
            return true;
        } else {
            return false;
        }
    }
    
    /*
     * 扑克牌
     * 
     * @return true: one赢，否则one输
     */
    public static function playingCards(&$returnData = []) {
        $color = ['A', 'B', 'C', 'D']; // A:黑桃, B:红桃, C:梅花, D:方片
        $number = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]; // 11:J, 12:Q, 13:K, 14:A
        
        $arg1 = [];
        $color_rand = mt_rand(0, 3);
        $arg1['color'] = $color[$color_rand];
        $number_rand = mt_rand(0, 12);
        $arg1['number'] = $number[$number_rand];
        
        // 防止生成相同的值
        unset($color[$color_rand]);
        sort($color);
        
        unset($number[$number_rand]);
        sort($number);
        
        $arg2 = [];
        $color_rand = mt_rand(0, 2);
        $arg2['color'] = $color[$color_rand];
        $number_rand = mt_rand(0, 11);
        $arg2['number'] = $number[$number_rand];

        $returnData['one'] = $arg1;
        $returnData['two'] = $arg2;
        
        // one 赢
        if ($arg1['number'] > $arg2['number']) {
            return true;
        } elseif ($arg1['number'] < $arg2['number']) {
            return false;
        }
    }
    
    // 下注 - 用户下注，把数据保存到session中
    // ['user_id'=>111, 'nickname'=>'', 'headimg'=>'', 'data'=>[[1,3,3], [2,2,3]]],
    public static function bet($client_id, $data) {
        $_SESSION['data'] = $data;
    }
   
    /**
     * 成功处理
     * @param string    $info   返回的信息
     * @param array     $data   返回的数据
     * @param string    $type   接口类型
     */
    private static function suc($info, $data, $type) {
        $new_message = ['status'=>1, 'info'=>$info, 'data'=>$data, 'type'=>$type,];
        Gateway::sendToCurrentClient(json_encode($new_message));
        return;
    }
    
    /**
     * 失败处理
     * @param string    $info   返回的信息
     * @param array     $data   返回的数据
     * @param string    $type   接口类型
     */
    private static function err($info, $data, $type) {
        $new_message = ['status'=>0, 'info'=>$info, 'data'=>$data, 'type'=>$type,];
        Gateway::sendToCurrentClient(json_encode($new_message));
        return;
    }
}
